/*---------------------------------------------------------------------------*
 * Copyright (C) 2014 Ryan M. Murphy <ryan.m.murphy.77@gmail.com>
 *
 * File Name   : usr_chrg.c
 *
 * Project     : Watts-rock
 *               <http://code.google.com/p/watts-rock-motherboard/>
 *
 * Description : This file is responsible for performing all battery charging
 *               operations.
 *
 * Last Update : Jan 4, 2014
 *---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*
 * Include Files
 *---------------------------------------------------------------------------*/
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "arb_error.h"
#include "arb_thread.h"
#include "arb_semaphore.h"
#include "arb_device.h"
#include "arb_sysTimer.h"
#include "arb_mailbox.h"
#include "arb_printf.h"
#include "utl_gpio.h"
#include "utl_eeprom.h"
#include "utl_timer.h"
#include "utl_stateMachine.h"
#include "drv_signal.h"
#include "drv_chrg.h"
#include "usr_chrg.h"

/*---------------------------------------------------------------------------*
 * Private Defines
 *---------------------------------------------------------------------------*/
#define CHRG_SOFTWARE_VERSION_NUMBER       ("april_7_2013_v1")
#define CHRG_DEFAULT_BATTERY_SERIAL_NUMBER (0xFFFFFFFF)
#define CHRG_DEFAULT_BOARD_SERIAL_NUMBER   (0xFFFFFFFF)

/*---------------------------------------------------------------------------*
 * The thread update rate in system timer ticks
 *---------------------------------------------------------------------------*/
#define CHRG_SLEEP_TICKS        (2)
#define CHRG_MAILBOX_SIZE       (36)

/*---------------------------------------------------------------------------*
 * Factory default battery voltage, current, and temperature limits
 *---------------------------------------------------------------------------*/
#define CHRG_MIN_HIGH_CURRENT_THR (1.0f)
#define CHRG_MAX_HIGH_CURRENT_THR (30.0f)
#define CHRG_MIN_LOW_VOLTAGE_THR  (40.0f)
#define CHRG_MAX_LOW_VOLTAGE_THR  (45.0f)
#define CHRG_MIN_HIGH_VOLTAGE_THR (50.0f)
#define CHRG_MAX_HIGH_VOLTAGE_THR (55.0f)
#define CHRG_MIN_HIGH_TEMP_THR    (80.0f)
#define CHRG_MAX_HIGH_TEMP_THR    (120.0f)
#define CHRG_MIN_VOLT_HYST        (3.0f)
#define CHRG_MAX_VOLT_HYST        (10.0f)
#define CHRG_MIN_TEMP_HYST        (3.0f)
#define CHRG_MAX_TEMP_HYST        (50.0f)
#define CHRG_MAX_CHRG_CURRENT     (6.0f)
#define CHRG_MIN_CHRG_CURRENT     (1.0f)
#define CHRG_EEPROM_ID            (0)
#define CHRG_BATT_FAULT_VLT_THR   (5.0f)
#define CHRG_BATT_FAULT_CURR_THR  (.005f)
#define CHRG_RETAIN_CHRG_STATE    (1)
#define CHRG_MPP_UPDATE_RATE      (1.0f) /*Seconds*/
#define CHRG_MPP_DELTA_PWM        (.01f)
#define CHRG_MPP_MIN_CURRENT      (.05f) /*Amps*/
#define CHRG_CV_EXIT_CURRENT      (.25f) /*Amps*/
#define CHRG_CURR_AVR_FORG_FACT   (1.0f-(1.0f/8.0f))

/*---------------------------------------------------------------------------*
 * Private Typedefs
 *---------------------------------------------------------------------------*/
typedef enum
{
   /*------------------------------------------------------------------------*
    * Charger feedback loop is waiting for a command.
    *------------------------------------------------------------------------*/
   CHARGING_IDLE = 0,
   /*------------------------------------------------------------------------*
    * The PWM is being controlled manually.
    *------------------------------------------------------------------------*/
   CHARGING_MAN,
   /*------------------------------------------------------------------------*
    * Slowly integrate up the PWM level until the battery starts drawing
    * current. This is required when entering MPP mode because the system
    * doesn't not know the PWM setting required to start pushing some current
    * into the battery.
    *------------------------------------------------------------------------*/
   CHARGING_MPP_RAMP_UP,
   /*------------------------------------------------------------------------*
    * Charge the battery using MPP or first order tracking.
    *------------------------------------------------------------------------*/
   CHARGING_TRACK,
   /*------------------------------------------------------------------------*
    * In this mode, 'f_loopFltrInt' is ramp down to 0 at a rate determined by
    * 'f_alpha'. Ramping down mitigates large off transients that violate the
    * drain to source dv/dt requirements of most MOSFETs.
    *------------------------------------------------------------------------*/
   CHARGING_RAMP_DOWN,

}t_chrgCntrlState;

typedef struct
{
   /*------------------------------------------------------------------------*
    * First order integration of the SEPIC PWM duty cycle.
    *------------------------------------------------------------------------*/
   float f_loopFltrInt;

   /*------------------------------------------------------------------------*
    * Control over the PWM duty cycle when in manual mode.
    *------------------------------------------------------------------------*/
   float f_manPwm;

   /*------------------------------------------------------------------------*
    * The maximum duty cycle allowed by the system.
    *------------------------------------------------------------------------*/
   float f_maxDuty;

   /*------------------------------------------------------------------------*
    * The exponentially weighted time constant which determines the amount
    * of averaging performed by the loop filter. Which can be found with the
    * following equation: f_alpha = dt / (T + dt), where dt = the CHRG update
    * rate, and T is the loop filter time constant. If we make T = X*dt then
    * the equation simplifies to: f_alpha = 1/ (1 + X), where X is the number
    * of consecutive CHRG frames (dt) that we want to filter over.
    * See http://en.wikipedia.org/wiki/Low-pass_filter for more information.
    *------------------------------------------------------------------------*/
   float f_alpha;

   /*------------------------------------------------------------------------*
    * This variable sets the various operational modes of the charger
    * feedback loop.
    *------------------------------------------------------------------------*/
   t_chrgCntrlState t_state;

   /*------------------------------------------------------------------------*
    * The amount of time the battery has been charging.
    *------------------------------------------------------------------------*/
   float f_chargeTime;

   /*------------------------------------------------------------------------*
    * The amount of time the systems waits (after it has detected no sun)
    * before allowing the charger to run again.
    *------------------------------------------------------------------------*/
   float f_noSunWaitTimer;

   /*------------------------------------------------------------------------*
    * The maximum amount of current allowed for a particular state.
    *------------------------------------------------------------------------*/
   float f_maxChargeCurrent;

   /*------------------------------------------------------------------------*
    * The update rate of the MPP tracking algorithm.
    *------------------------------------------------------------------------*/
   float f_mppUpdateTimer;

   /*------------------------------------------------------------------------*
    * The current from the last time the MPP algorithm ran.
    *------------------------------------------------------------------------*/
   float f_prevCurrent;

   /*------------------------------------------------------------------------*
    * In MPP tracking mode, this is the direction of pwm integration.
    *------------------------------------------------------------------------*/
   float f_mppPwmDir;

}t_chgCntrl;

typedef struct
{
   /*------------------------------------------------------------------------*
    * The serial number for the battery this particular CHRG is attached to.
    *------------------------------------------------------------------------*/
   uint32_t i_battSerialNumber;
   /*------------------------------------------------------------------------*
    * The serial number for the particular CHRG board this software is running
    * on.
    *------------------------------------------------------------------------*/
   uint32_t i_boardSerialNumber;
   /*------------------------------------------------------------------------*
    * The current software version number loaded on CHRG board with serial
    * number 'i_boardSerialNumber'
    *------------------------------------------------------------------------*/
   //uint32_t i_softwareVersionNumber;
   /*------------------------------------------------------------------------*
    * The voltage, current, and temperature limits for this particular
    * battery.
    *------------------------------------------------------------------------*/
   t_chrgLimits t_limits;
   /*------------------------------------------------------------------------*
    * The voltage, current, and temperature calibration coefficients for this
    * particular battery management system.
    *------------------------------------------------------------------------*/
   t_chrgCal t_cal;

}t_chrgEeprom;

typedef struct
{
   /*------------------------------------------------------------------------*
    * Resources that can be shared amongst multiple users (either a global
    * buffer or IO device) need to be protected against race conditions. We
    * use this semaphore for just that purpose.
    *------------------------------------------------------------------------*/
   t_SEMHANDLE t_mutex;
   /*------------------------------------------------------------------------*
    * This semaphore is set externally, and is used for transitioning the CHRG
    * out of a particular "over-limit" state.
    *------------------------------------------------------------------------*/
   t_SEMHANDLE t_chrgLimitAck;
   /*------------------------------------------------------------------------*
    * Handle to the console thread
    *------------------------------------------------------------------------*/
   t_THRDHANDLE t_chrgThread;
   /*------------------------------------------------------------------------*
    * Handle to the LED driver.
    *------------------------------------------------------------------------*/
   t_DEVHANDLE t_signalHndl;
   /*------------------------------------------------------------------------*
    * Handle to the CHRG driver.
    *------------------------------------------------------------------------*/
   t_DEVHANDLE t_chrgHndl;
   /*------------------------------------------------------------------------*
    * Handle to the state-machine object that defines how the CHRG operates.
    *------------------------------------------------------------------------*/
   t_STMNHNDL t_stateMn;
   /*------------------------------------------------------------------------*
    * Handle to the EEPROM block where 't_eeprom' is being stored.
    *------------------------------------------------------------------------*/
   t_EEPROMHANDLE t_eepromHndl;
   /*------------------------------------------------------------------------*
    * The mailbox for incoming messages to the CHRG.
    *------------------------------------------------------------------------*/
   t_MAILBOXHNDL t_chrgInMbxHndl;
   /*------------------------------------------------------------------------*
    * The mailbox for outgoing messages from the CHRG.
    *------------------------------------------------------------------------*/
   t_MAILBOXHNDL t_chrgOutMbxHndl;
   /*------------------------------------------------------------------------*
    * The last known 'state' of the CHRG.
    *------------------------------------------------------------------------*/
   t_chrgState t_prevState;
   /*------------------------------------------------------------------------*
    * The current 'state' of the CHRG.
    *------------------------------------------------------------------------*/
   t_chrgState t_currState;
   /*------------------------------------------------------------------------*
    * The measured battery voltage, current, temperature, and flags signifying
    * the type of signal (AC or DC) connected to the charger.
    *------------------------------------------------------------------------*/
   t_chrgMeas t_bMeas;
   /*------------------------------------------------------------------------*
    * Variables that get stored in EEPROM.
    *------------------------------------------------------------------------*/
   volatile t_chrgEeprom t_eeprom;
   /*------------------------------------------------------------------------*
    * The last time the CHRG thread ran.
    *------------------------------------------------------------------------*/
   t_sysTime t_prevTime;
   /*------------------------------------------------------------------------*
    * The current time step of CHRG thread.
    *------------------------------------------------------------------------*/
   float f_dt;
   /*------------------------------------------------------------------------*
    * This variable stores the time at which the CHRG first enters a new state.
    *------------------------------------------------------------------------*/
   t_sysTime t_startTime;
   /*------------------------------------------------------------------------*
    * The amount of time left until the CHRG powers down the motherboard
    * during the deep discharge state.
    *------------------------------------------------------------------------*/
   int8_t c_deepDisTimeRem;
   /*------------------------------------------------------------------------*
    * This variables controls all aspects of the charging feedback loop.
    *------------------------------------------------------------------------*/
   t_chgCntrl t_charge;
   /*------------------------------------------------------------------------*
    * The fault status where,
    *              .---.---.---.---.---.---.---.---.
    *    c_fault = | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
    *              '---'---'---'---'---'---'---'---'
    *                            |   |   |   |   |
    *                            |   |   |   |   '-->BMS_FAULT_CURRENT
    *                            |   |   |   '------>BMS_FAULT_TEMP
    *                            |   |   '---------->BMS_FAULT_NO_BATT
    *                            |   '-------------->BMS_FAULT_CHARGE
    *                            '------------------>BMS_FAULT_RAPID_TEMP
    *------------------------------------------------------------------------*/
   uint8_t c_fault;

}t_chrgObjct;

/*---------------------------------------------------------------------------*
 * Private Function Prototypes
 *---------------------------------------------------------------------------*/
static float usr_getInstCurrentAndCountCoulombs( void);
static void usr_chrgCheckForTrickleCharge( void);
static void usr_chrgFaultMonitor( void);
static bool usr_setBmsLimits( t_chrgLimits *pt_limits);
static void usr_chrgCollectMeas( void);
static void usr_storeEepromFactDefaults( void);
static void usr_getValuesFromEeprom( void);
static bool usr_chrgChrgLoopFilterRampDown( void);
static void usr_chrgChrgFirstOrderLoopFilter( float f_setPoint,
                                              float f_compareValue);
static void usr_chrgBoot( int32_t i_stateData,
                          uint8_t c_prevState);
static void usr_chrgCal( int32_t i_stateData,
                         uint8_t c_prevState);
static void usr_chrgCalFinished( int32_t i_stateData,
                                 uint8_t c_prevState);
static void usr_chrgIdle( int32_t i_stateData,
                          uint8_t c_prevState);
static void usr_chrgMppCharge( int32_t i_stateData,
                               uint8_t c_prevState);
static void usr_chrgCcCharge( int32_t i_stateData,
                              uint8_t c_prevState);
static void usr_chrgCvCharge( int32_t i_stateData,
                              uint8_t c_prevState);
static void usr_chrgManCharge( int32_t i_stateData,
                               uint8_t c_prevState);
static void usr_chrgFault( int32_t i_stateData,
                           uint8_t c_prevState);
static void usr_chrgPowerDown( int32_t i_stateData,
                               uint8_t c_prevState);
static void usr_chrgCheckForMessage( void);
static void usr_chrg( t_parameters t_param,
                      t_arguments  t_args);
static void usr_chrgStateMachineInit( void);
static void usr_chrgChrgManLoopFilter(float f_manPwm);

/*---------------------------------------------------------------------------*
 * Private Global Variables
 *---------------------------------------------------------------------------*/
static t_chrgObjct gt_chrgObjct;

/*---------------------------------------------------------------------------*
 * Private Function Definitions
 *---------------------------------------------------------------------------*/
static float usr_getInstCurrentAndCountCoulombs( void)
{
   float f_instCurrent;

   f_instCurrent = gt_chrgObjct.t_bMeas.f_current;
   if( f_instCurrent < 0)
   {
      f_instCurrent = 0.0f; /*Don't allow negative current to be tracked*/
   }

   f_instCurrent = fabs(f_instCurrent);

   return f_instCurrent;

}/*End usr_getInstCurrentAndCountCoulombs*/

static void usr_chrgCheckForTrickleCharge( void)
{
   if( gt_chrgObjct.t_bMeas.f_volts <= gt_chrgObjct.t_eeprom.t_limits.
       f_lowVoltageThr)
   {
      /*---------------------------------------------------------------------*
       * Charge with 10% of max charge current.
       *---------------------------------------------------------------------*/
      gt_chrgObjct.t_charge.f_maxChargeCurrent = .10f*
      gt_chrgObjct.t_eeprom.t_limits.f_maxChargeCurrent;

   }
   else if( gt_chrgObjct.t_bMeas.f_volts >= (gt_chrgObjct.t_eeprom.t_limits.
   f_lowVoltageThr + gt_chrgObjct.t_eeprom.t_limits.f_voltHyst))
   {
      gt_chrgObjct.t_charge.f_maxChargeCurrent =
      gt_chrgObjct.t_eeprom.t_limits.f_maxChargeCurrent;

   }

}/*End usr_chrgCheckForTrickleCharge*/

static void usr_chrgFaultMonitor( void)
{
   /*------------------------------------------------------------------------*
    * Is there a battery connected? Only check this state if we are not in
    * manual charge mode.
    *------------------------------------------------------------------------*/
   if( gt_chrgObjct.t_bMeas.f_volts <= CHRG_BATT_FAULT_VLT_THR)
   {
      gt_chrgObjct.c_fault |= BMS_FAULT_NO_BATT;
   }
   else
      gt_chrgObjct.c_fault &= ~BMS_FAULT_NO_BATT;

   /*------------------------------------------------------------------------*
    * Is the charger running for a period of time? If so, monitor the current
    * to make sure its negative, if its not then the charge port might be open
    * circuit. If open, a fault will be thrown and won't be reset until an
    * external user acknowledges the issue.
    *------------------------------------------------------------------------*/
   //if( (gt_chrgObjct.t_charge.t_state != CHARGING_IDLE) &&
   //    (gt_chrgObjct.t_charge.f_chargeTime > 10.0f))
   //{
   //   if( gt_chrgObjct.t_bMeas.f_current > 0)
   //      gt_chrgObjct.c_fault |= BMS_FAULT_CHARGE;

   //}

   /*------------------------------------------------------------------------*
    * Is the system pulling too much current? If yes, the fault condition
    * is only cleared once user acknowledges the problem.
    *------------------------------------------------------------------------*/
   if( fabs(gt_chrgObjct.t_bMeas.f_avrCurrent) >= gt_chrgObjct.t_eeprom.t_limits.
   f_highCurrThr)
   {
      gt_chrgObjct.c_fault |= BMS_FAULT_CURRENT;
   }

   /*------------------------------------------------------------------------*
    * Is the system within temperature limits?
    *------------------------------------------------------------------------*/
   if( gt_chrgObjct.t_bMeas.f_temp >= gt_chrgObjct.t_eeprom.t_limits.
   f_highTempThr)
   {
      gt_chrgObjct.c_fault |= BMS_FAULT_TEMP;
   }
   else if( gt_chrgObjct.t_bMeas.f_temp < (gt_chrgObjct.t_eeprom.t_limits.
            f_highTempThr - gt_chrgObjct.t_eeprom.t_limits.f_tempHyst))
   {
      gt_chrgObjct.c_fault &= ~BMS_FAULT_TEMP;
   }

}/*End usr_chrgFaultMonitor*/

t_chrgLimits *usr_getBmsLimits( void)
{
   return (t_chrgLimits *)&gt_chrgObjct.t_eeprom.t_limits;
}/*End usr_getBmsLimits*/

/*---------------------------------------------------------------------------*
 * Configures the absolute battery voltage, current, and temperature limits.
 *---------------------------------------------------------------------------*/
static bool usr_setBmsLimits( t_chrgLimits *pt_limits)
{
   bool b_valid = true;
   t_eepromError t_err;

   if( (pt_limits->f_lowVoltageThr < CHRG_MIN_LOW_VOLTAGE_THR) ||
       (pt_limits->f_lowVoltageThr > CHRG_MAX_LOW_VOLTAGE_THR) ||
       (pt_limits->f_highVoltageThr < CHRG_MIN_HIGH_VOLTAGE_THR) ||
       (pt_limits->f_highVoltageThr > CHRG_MAX_HIGH_VOLTAGE_THR))
   {
      b_valid = false;
   }
   else if( (pt_limits->f_highCurrThr < CHRG_MIN_HIGH_CURRENT_THR) ||
            (pt_limits->f_highCurrThr > CHRG_MAX_HIGH_CURRENT_THR))
   {
      b_valid = false;
   }
   else if( (pt_limits->f_highTempThr < CHRG_MIN_HIGH_TEMP_THR) ||
            (pt_limits->f_highTempThr > CHRG_MAX_HIGH_TEMP_THR))
   {
      b_valid = false;
   }
   else if( (pt_limits->f_maxChargeCurrent < CHRG_MIN_CHRG_CURRENT) ||
   (pt_limits->f_maxChargeCurrent > CHRG_MAX_CHRG_CURRENT))
   {
      b_valid = false;
   }

   if( b_valid == true)
   {
      gt_chrgObjct.t_eeprom.t_limits.f_lowVoltageThr  =
      pt_limits->f_lowVoltageThr; /*Volts*/
      gt_chrgObjct.t_eeprom.t_limits.f_highVoltageThr =
      pt_limits->f_highVoltageThr; /*Volts*/
      gt_chrgObjct.t_eeprom.t_limits.f_highCurrThr    =
      pt_limits->f_highCurrThr; /*Amps*/
      gt_chrgObjct.t_eeprom.t_limits.f_highTempThr    =
      pt_limits->f_highTempThr; /*degrees C*/
      gt_chrgObjct.t_eeprom.t_limits.f_maxChargeCurrent   =
      pt_limits->f_maxChargeCurrent; /*Amps*/

      /*---------------------------------------------------------------------*
       * Store the new settings in EEPROM...
       *---------------------------------------------------------------------*/
      t_err = utl_writeEeprom( gt_chrgObjct.t_eepromHndl,
                               (uint8_t *)&gt_chrgObjct.t_eeprom,
                               (uint16_t)sizeof( gt_chrgObjct.t_eeprom));

      if( t_err < 0)
         exit(0);
   }

   return b_valid;

}/*End usr_setBmsLimits*/

static void usr_chrgCollectMeas( void)
{
   char ac_buff[120];
   t_chrgMeas t_meas;
   t_sysTime t_time;
   uint16_t s_size;

   t_time = arb_sysTimeNow();

   /*------------------------------------------------------------------------*
    * Keep track of how long its been since the last time this function was
    * called - this is the update rate of 'usr_chrg'. When first coming out of
    * reset, gt_chrgObjct.t_prevTime is not valid so don't calculate.
    *------------------------------------------------------------------------*/
   if( gt_chrgObjct.t_prevTime.l_ticks > 0)
   {
      gt_chrgObjct.f_dt = ((float)t_time.l_ticks -
      (float)gt_chrgObjct.t_prevTime.l_ticks) / ARB_SYS_TIMER_TICK_RATE;
   }
   else
      gt_chrgObjct.f_dt = 0;

   gt_chrgObjct.t_prevTime = t_time;

   /*------------------------------------------------------------------------*
    * Collect new voltage, current, and temperature measurements, as well
    * as monitoring for the charger being connected.
    *------------------------------------------------------------------------*/
//utl_gpioOn( GPIO_PORTC,
//            PIN_6);

   arb_ioctl( gt_chrgObjct.t_chrgHndl,
              CHRG_GET_MEAS,
              (uint32_t)((uint16_t)&t_meas));

//utl_gpioOff( GPIO_PORTC,
//             PIN_6);

   /*------------------------------------------------------------------------*
    * Store the results...
    *------------------------------------------------------------------------*/
   if( t_meas.f_current < 0.0f)
      t_meas.f_current = 0.0f;

   if( t_meas.f_sepicVolts < 0.0f)
      t_meas.f_sepicVolts = 0.0f;

   if( t_meas.f_volts < 0.0f)
      t_meas.f_volts = 0.0f;

   gt_chrgObjct.t_bMeas.b_acChargeDet  = t_meas.b_acChargeDet;
   gt_chrgObjct.t_bMeas.b_dcChargeDet  = t_meas.b_dcChargeDet;
   gt_chrgObjct.t_bMeas.f_current      = t_meas.f_current;
   gt_chrgObjct.t_bMeas.f_sepicVolts   = t_meas.f_sepicVolts;
   gt_chrgObjct.t_bMeas.f_temp         = t_meas.f_temp;
   gt_chrgObjct.t_bMeas.f_volts        = t_meas.f_volts;

   gt_chrgObjct.t_bMeas.f_avrCurrent =
   gt_chrgObjct.t_bMeas.f_avrCurrent*CHRG_CURR_AVR_FORG_FACT +
   (1.0f - CHRG_CURR_AVR_FORG_FACT)*t_meas.f_current;


   /*------------------------------------------------------------------------*
    * If enabled, send debug to the console...
    *------------------------------------------------------------------------*/
   s_size = sprintf(( char *)ac_buff, "Output Volts= %2.5f Input Volts= %2.5f Output Amps= %2.5f Temp C= %3.5f Duty = %.4f\r",
   (double)gt_chrgObjct.t_bMeas.f_volts,
   (double)gt_chrgObjct.t_bMeas.f_sepicVolts,
   (double)gt_chrgObjct.t_bMeas.f_avrCurrent,
   (double)gt_chrgObjct.t_bMeas.f_temp,
   (double)gt_chrgObjct.t_charge.f_loopFltrInt);
   arb_printf( PRINTF_DBG_LOW | PRINTF_DBG_SHOW_TIME,
               (const char *)ac_buff);

}/*End usr_chrgCollectMeas*/

static void usr_storeEepromFactDefaults( void)
{
   t_eepromError t_err;

   /*---------------------------------------------------------------------------*
    * Initialize the default board and battery version numbers
    *---------------------------------------------------------------------------*/
   gt_chrgObjct.t_eeprom.i_battSerialNumber = CHRG_DEFAULT_BATTERY_SERIAL_NUMBER;
   gt_chrgObjct.t_eeprom.i_boardSerialNumber = CHRG_DEFAULT_BOARD_SERIAL_NUMBER;
   //gt_chrgObjct.t_eeprom.i_softwareVersionNumber = (uint32_t)CHRG_SOFTWARE_VERSION_NUMBER;

   /*---------------------------------------------------------------------------*
    * Initialize the volt and current meter calibration factors.
    *---------------------------------------------------------------------------*/
   gt_chrgObjct.t_eeprom.t_cal.f_calVltMtrBias         = 0.0006f;   /*Volts*/
   gt_chrgObjct.t_eeprom.t_cal.f_calVltMtrRDivide      = 41.08f;
   gt_chrgObjct.t_eeprom.t_cal.f_calCurMtrBias         = -.00085f;  /*Volts*/
   gt_chrgObjct.t_eeprom.t_cal.f_calRsense             = .03f;      /*Ohms*/
   gt_chrgObjct.t_eeprom.t_cal.f_calTempMtrBias        = -0.00405f; /*Volts*/
   gt_chrgObjct.t_eeprom.t_cal.f_calSepicVltMtrBias    = 0.00075f;  /*Volts*/
   gt_chrgObjct.t_eeprom.t_cal.f_calSepicVltMtrRDivide = 41.08f;

   /*---------------------------------------------------------------------------*
    * Initialize the battery limits to the CHRG 'birth date'.
    *---------------------------------------------------------------------------*/
   gt_chrgObjct.t_eeprom.t_limits.f_lowVoltageThr    = 41.0f; /*Volts*/
   gt_chrgObjct.t_eeprom.t_limits.f_highVoltageThr   = 52.5f; /*Volts*/
   gt_chrgObjct.t_eeprom.t_limits.f_highCurrThr      = 10.0f; /*Amps*/
   gt_chrgObjct.t_eeprom.t_limits.f_highTempThr      = 90.0f; /*degrees C*/
   gt_chrgObjct.t_eeprom.t_limits.f_tempHyst         = 5.0f;  /*degrees C*/
   gt_chrgObjct.t_eeprom.t_limits.f_voltHyst         = 1.0f;  /*Volts*/
   gt_chrgObjct.t_eeprom.t_limits.f_maxChargeCurrent = 3.0f;  /*Amps*/

   /*---------------------------------------------------------------------------*
    * Store the factory defaults back into the eeprom.
    *---------------------------------------------------------------------------*/
   t_err = utl_writeEeprom( gt_chrgObjct.t_eepromHndl,
                            (uint8_t *)&gt_chrgObjct.t_eeprom,
                            (uint16_t)sizeof( gt_chrgObjct.t_eeprom));

   if( t_err < 0)
      exit(0);

}/*End usr_storeEepromFactDefaults*/

static void usr_getValuesFromEeprom( void)
{
   t_eepromError t_err;

   t_err = utl_readEeprom( gt_chrgObjct.t_eepromHndl,
                           (uint8_t *)&gt_chrgObjct.t_eeprom,
                           (uint16_t)sizeof( gt_chrgObjct.t_eeprom));

   if( t_err < 0)
   {
      arb_printf( PRINTF_DBG_HIGH,
                  "Failed to load EEPROM\n\r");
      exit(0);
   }

}/*End usr_getValuesFromEeprom*/

static bool usr_chrgChrgLoopFilterRampDown( void)
{
   gt_chrgObjct.t_charge.f_loopFltrInt = gt_chrgObjct.t_charge.f_loopFltrInt*.7f;

   arb_ioctl( gt_chrgObjct.t_chrgHndl,
              CHRG_CHARGER_SET_DUTY_CYCLE,
              (uint32_t)((uint16_t)&gt_chrgObjct.t_charge.f_loopFltrInt));

   if( gt_chrgObjct.t_charge.f_loopFltrInt < .001f)
   {
      gt_chrgObjct.t_charge.f_loopFltrInt = 0.0f;

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                 CHRG_CHARGER_SET_DUTY_CYCLE,
                 (uint32_t)((uint16_t)&gt_chrgObjct.t_charge.f_loopFltrInt));

      return true;
   }

   return false;

}/*End usr_chrgChrgLoopFilterRampDown*/

static void usr_chrgChrgFirstOrderLoopFilter( float f_setPoint,
                                             float f_compareValue)
{

   float f_dutyAdjust;

   /*------------------------------------------------------------------------*
    * Calculate the percentage error in current.
    *------------------------------------------------------------------------*/
   f_dutyAdjust = (f_setPoint - f_compareValue) / f_setPoint;

   /*------------------------------------------------------------------------*
    * Perform first order filtering...
    *------------------------------------------------------------------------*/
   gt_chrgObjct.t_charge.f_loopFltrInt += (f_dutyAdjust*gt_chrgObjct.t_charge.
   f_alpha);

   if( gt_chrgObjct.t_charge.f_loopFltrInt > gt_chrgObjct.t_charge.f_maxDuty)
      gt_chrgObjct.t_charge.f_loopFltrInt = gt_chrgObjct.t_charge.f_maxDuty;

   arb_ioctl( gt_chrgObjct.t_chrgHndl,
               CHRG_CHARGER_SET_DUTY_CYCLE,
               (uint32_t)((uint16_t)&gt_chrgObjct.t_charge.f_loopFltrInt));

}/*End usr_chrgChrgFirstOrderLoopFilter*/

static void usr_chrgChrgManLoopFilter(float f_manPwm)
{
   float f_temp1;
   float f_temp2;

   f_temp1 = (1.0f-gt_chrgObjct.t_charge.f_alpha)*gt_chrgObjct.t_charge.
   f_loopFltrInt;
   f_temp2 = gt_chrgObjct.t_charge.f_alpha*f_manPwm;

   /*------------------------------------------------------------------------*
    * Perform first order filtering...
    *------------------------------------------------------------------------*/
   gt_chrgObjct.t_charge.f_loopFltrInt = (f_temp1 + f_temp2);

   if( gt_chrgObjct.t_charge.f_loopFltrInt > gt_chrgObjct.t_charge.f_maxDuty)
      gt_chrgObjct.t_charge.f_loopFltrInt = gt_chrgObjct.t_charge.f_maxDuty;
   else if( gt_chrgObjct.t_charge.f_loopFltrInt < 0.0f)
      gt_chrgObjct.t_charge.f_loopFltrInt = 0.0f;

   arb_ioctl( gt_chrgObjct.t_chrgHndl,
               CHRG_CHARGER_SET_DUTY_CYCLE,
               (uint32_t)((uint16_t)&gt_chrgObjct.t_charge.f_loopFltrInt));

}/*End usr_chrgChrgFirstOrderLoopFilter*/

static void usr_chrgBoot( int32_t i_stateData,
                         uint8_t c_prevState)
{
   t_stmnError t_stErr;

   if( c_prevState != CHRG_BOOT)
   {
      /*---------------------------------------------------------------------*
       * Make sure the motherboard stays on by sending a logic one to the
       * voltage regulator controller.
       *---------------------------------------------------------------------*/
      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                 CHRG_PWR_CNTRL,
                 CHRG_DEVICE_ON);

      /*---------------------------------------------------------------------*
       * Make sure the charge and discharge FETs are turned off...
       *---------------------------------------------------------------------*/
      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                 CHRG_CHARGER_CNTRL,
                 CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                  CHRG_DC_CHRG_CNTRL,
                  CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                  CHRG_AC_CHRG_CNTRL,
                  CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                 CHRG_FAN_CNTRL,
                 CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 ALL_LEDS_OFF,
                 0);

      gt_chrgObjct.t_currState = CHRG_BOOT;

      /*---------------------------------------------------------------------*
       * Keep track of where we came from...
       *---------------------------------------------------------------------*/
      gt_chrgObjct.t_prevState = (t_chrgState)c_prevState;

   }/*End if( c_prevState != CHRG_BOOT)*/

   /*------------------------------------------------------------------------*
    * Determine the next state to transition to...
    *------------------------------------------------------------------------*/
   if( gt_chrgObjct.c_fault)
   {
      t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                     (uint8_t)CHRG_FAULT,
                                     0);
   }
   else
   {
      t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                     (uint8_t)CHRG_IDLE,
                                     0);
   }

}/*End usr_chrgBoot*/

static void usr_chrgCal( int32_t i_stateData,
                        uint8_t c_prevState)
{
   //t_stmnError t_stErr;

   if( c_prevState != CHRG_CAL)
   {
      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 ALL_LEDS_OFF,
                 0);

      gt_chrgObjct.t_currState = CHRG_CAL;

      /*---------------------------------------------------------------------*
       * Keep track of where we came from...
       *---------------------------------------------------------------------*/
      gt_chrgObjct.t_prevState = (t_chrgState)c_prevState;

   }

}/*End usr_chrgCal*/

static void usr_chrgCalFinished( int32_t i_stateData,
                                uint8_t c_prevState)
{
   t_stmnError t_stErr;

   if( c_prevState != CHRG_CAL_FINISHED)
   {
      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 ALL_LEDS_OFF,
                 0);

      gt_chrgObjct.t_currState = CHRG_CAL_FINISHED;

      /*---------------------------------------------------------------------*
       * Keep track of where we came from...
       *---------------------------------------------------------------------*/
      gt_chrgObjct.t_prevState = (t_chrgState)c_prevState;

      /*---------------------------------------------------------------------*
       * Tell the driver about the new calibration parameters and store the
       * new data in EEPROM.
       *---------------------------------------------------------------------*/
   }

   /*------------------------------------------------------------------------*
    * Since the calibration can only be entered from the nominal state, then
    * return to the nominal state.
    *------------------------------------------------------------------------*/
   t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                  (uint8_t)CHRG_IDLE,
                                  0);

}/*End usr_chrgCalFinished*/

static void usr_chrgIdle( int32_t i_stateData,
                            uint8_t c_prevState)
{
   t_stmnError t_stErr;
   bool b_chrgDet;
   static bool b_fanEn = false;

   if( c_prevState != CHRG_IDLE)
   {
      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 ALL_LEDS_OFF,
                 0);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                 CHRG_CHARGER_CNTRL,
                 CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                 CHRG_FAN_CNTRL,
                 CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                 CHRG_AC_CHRG_CNTRL,
                 CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                 CHRG_DC_CHRG_CNTRL,
                 CHRG_DEVICE_OFF);

      gt_chrgObjct.t_currState = CHRG_IDLE;

      /*---------------------------------------------------------------------*
       * Keep track of where we came from...
       *---------------------------------------------------------------------*/
      gt_chrgObjct.t_prevState = (t_chrgState)c_prevState;

      /*---------------------------------------------------------------------*
       * Display indication that the CHRG is in the nominal state.
       *---------------------------------------------------------------------*/
      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 RED_LED_ON,
                 0);

      b_fanEn = false;

   }/*End if( c_prevState != CHRG_IDLE)*/

   if( gt_chrgObjct.t_bMeas.f_temp >= 
       (gt_chrgObjct.t_eeprom.t_limits.f_highTempThr*.5))
   {
      if( b_fanEn == false)
      {
         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_FAN_CNTRL,
                    CHRG_DEVICE_ON);
         b_fanEn = true;
      }
   }
   else if( gt_chrgObjct.t_bMeas.f_temp <= 
            ((gt_chrgObjct.t_eeprom.t_limits.f_highTempThr*.5) - 
              gt_chrgObjct.t_eeprom.t_limits.f_tempHyst))
   {
      if( b_fanEn == true)
      {
         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_FAN_CNTRL,
                    CHRG_DEVICE_OFF);
         b_fanEn = false;
      }
   }

   b_chrgDet = gt_chrgObjct.t_bMeas.b_acChargeDet |
   gt_chrgObjct.t_bMeas.b_dcChargeDet;

   /*------------------------------------------------------------------------*
    * Determine the next state to transition to...
    *------------------------------------------------------------------------*/
   if( gt_chrgObjct.c_fault)
   {
      t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                     (uint8_t)CHRG_FAULT,
                                     0);
   }
   else if( (gt_chrgObjct.t_bMeas.f_volts < (gt_chrgObjct.t_eeprom.t_limits.
   f_highVoltageThr - gt_chrgObjct.t_eeprom.t_limits.f_voltHyst)) &&
   (b_chrgDet == true) && (gt_chrgObjct.t_charge.f_noSunWaitTimer == 0.0f))
   {
      t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                     (uint8_t)CHRG_MPP_CHARGE,
                                     0);
   }

}/*End usr_chrgIdle*/

static void usr_chrgMppCharge( int32_t i_stateData,
                              uint8_t c_prevState)
{
   t_stmnError t_stErr;
   float f_instCurrent;
   bool b_chrgDet;

   if( c_prevState != CHRG_MPP_CHARGE)
   {
      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 ALL_LEDS_OFF,
                 0);

      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 GREEN_LED_ON,
                 0);

      if( i_stateData != CHRG_RETAIN_CHRG_STATE)
      {

         float f_initDutyCycle = 0.0f;

         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_CHARGER_CNTRL,
                    CHRG_DEVICE_ON);

         if( gt_chrgObjct.t_bMeas.b_dcChargeDet == true)
         {

            arb_ioctl( gt_chrgObjct.t_chrgHndl,
                        CHRG_DC_CHRG_CNTRL,
                        CHRG_DEVICE_ON);
         }
         else if( gt_chrgObjct.t_bMeas.b_acChargeDet == true)
         {
            arb_ioctl( gt_chrgObjct.t_chrgHndl,
                        CHRG_AC_CHRG_CNTRL,
                        CHRG_DEVICE_ON);
         }

         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_FAN_CNTRL,
                    CHRG_DEVICE_ON);

         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_CHARGER_SET_DUTY_CYCLE,
                    (uint32_t)((uint16_t)&f_initDutyCycle));

         /*------------------------------------------------------------------*
          * Average 50 consecutive CHRG frames.
          *------------------------------------------------------------------*/
         gt_chrgObjct.t_charge.f_alpha            = .02f;
         gt_chrgObjct.t_charge.f_loopFltrInt      = 0.0f;
         gt_chrgObjct.t_charge.f_maxDuty          = .5f;
         gt_chrgObjct.t_charge.t_state            = CHARGING_MPP_RAMP_UP;
         gt_chrgObjct.t_charge.f_noSunWaitTimer   = 0.0f;
         gt_chrgObjct.t_charge.f_chargeTime       = 0.0f;
         gt_chrgObjct.t_charge.f_prevCurrent      = 0.0f;
         gt_chrgObjct.t_charge.f_mppUpdateTimer   = CHRG_MPP_UPDATE_RATE;
         gt_chrgObjct.t_charge.f_mppPwmDir        = 1.0f;
         gt_chrgObjct.t_charge.f_maxChargeCurrent =
         gt_chrgObjct.t_eeprom.t_limits.f_maxChargeCurrent;

      }/*End if( i_stateData != CHRG_RETAIN_CHRG_STATE)*/

      gt_chrgObjct.t_currState = CHRG_MPP_CHARGE;

      /*---------------------------------------------------------------------*
       * Keep track of where we came from...
       *---------------------------------------------------------------------*/
      gt_chrgObjct.t_prevState = (t_chrgState)c_prevState;

   }/*End if( c_prevState != CHRG_MPP_CHARGE)*/

   b_chrgDet = gt_chrgObjct.t_bMeas.b_acChargeDet |
   gt_chrgObjct.t_bMeas.b_dcChargeDet;

   /*------------------------------------------------------------------------*
    * Set the charge current low when the voltage is below the low threshold
    *------------------------------------------------------------------------*/
   usr_chrgCheckForTrickleCharge();

   /*------------------------------------------------------------------------*
    * Get the instantaneous current and keep track of the total number of
    * Coulombs the battery has been charged with over a lifetime of usage.
    *------------------------------------------------------------------------*/
   f_instCurrent = usr_getInstCurrentAndCountCoulombs();

   /*########################################################################*
    * Determine next state transition.
    *########################################################################*/
   if( gt_chrgObjct.c_fault)
   {
      /*---------------------------------------------------------------------*
       * Ramp down charger before changing states...
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.t_state == CHARGING_IDLE) /*Safe to change states?*/
      {

         t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                        (uint8_t)CHRG_FAULT,
                                        0);
      }
      else
         gt_chrgObjct.t_charge.t_state = CHARGING_RAMP_DOWN;
   }
   else if( gt_chrgObjct.t_bMeas.f_volts >= (gt_chrgObjct.t_eeprom.t_limits.
            f_highVoltageThr))
   {
      t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                     (uint8_t)CHRG_CV_CHARGE,
                                     (int32_t)CHRG_RETAIN_CHRG_STATE);
   }
   else if( f_instCurrent >= gt_chrgObjct.t_charge.f_maxChargeCurrent)
   {
      t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                     (uint8_t)CHRG_CC_CHARGE,
                                     (int32_t)CHRG_RETAIN_CHRG_STATE);
   }
   else if( b_chrgDet == false)
   {
      /*---------------------------------------------------------------------*
       * Ramp down charger before changing states...
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.t_state == CHARGING_IDLE) /*Safe to change states?*/
      {
         t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                        (uint8_t)CHRG_IDLE,
                                        0);
      }
      else
         gt_chrgObjct.t_charge.t_state = CHARGING_RAMP_DOWN;
   }
   else if( (f_instCurrent < CHRG_MPP_MIN_CURRENT) &&
            (gt_chrgObjct.t_charge.f_chargeTime >= 20.0f))
   {
      /*---------------------------------------------------------------------*
       * Not enough sun to continue charging...set the counter and check to
       * see if the sun returns once it expires.
       *---------------------------------------------------------------------*/
      gt_chrgObjct.t_charge.f_noSunWaitTimer = 30.0f;

      /*---------------------------------------------------------------------*
       * Ramp down charger before changing states...
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.t_state == CHARGING_IDLE) /*Safe to change states?*/
      {
         t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                        (uint8_t)CHRG_IDLE,
                                        0);
      }
      else
         gt_chrgObjct.t_charge.t_state = CHARGING_RAMP_DOWN;
   }

   switch( gt_chrgObjct.t_charge.t_state)
   {
      case CHARGING_MPP_RAMP_UP:

          gt_chrgObjct.t_charge.f_mppUpdateTimer -= gt_chrgObjct.f_dt;

          /*-----------------------------------------------------------------*
           * Is it time to run a new iteration of tracking?
           *-----------------------------------------------------------------*/
          if( gt_chrgObjct.t_charge.f_mppUpdateTimer < 0.0f)
          {

             gt_chrgObjct.t_charge.f_loopFltrInt += CHRG_MPP_DELTA_PWM;

             /*--------------------------------------------------------------*
              * Hard limit the range of PWM integration...
              *--------------------------------------------------------------*/
             if( gt_chrgObjct.t_charge.f_loopFltrInt > gt_chrgObjct.t_charge.f_maxDuty)
                gt_chrgObjct.t_charge.f_loopFltrInt = gt_chrgObjct.t_charge.f_maxDuty;
             else if( gt_chrgObjct.t_charge.f_loopFltrInt < 0)
                gt_chrgObjct.t_charge.f_loopFltrInt = 0.0f;

             /*--------------------------------------------------------------*
              * Reset the timer...
              *--------------------------------------------------------------*/
             gt_chrgObjct.t_charge.f_mppUpdateTimer = CHRG_MPP_UPDATE_RATE;

             /*--------------------------------------------------------------*
              * Change the current going into the SEPIC converter.
              *--------------------------------------------------------------*/
             arb_ioctl( gt_chrgObjct.t_chrgHndl,
                        CHRG_CHARGER_SET_DUTY_CYCLE,
                        (uint32_t)((uint16_t)&gt_chrgObjct.t_charge.f_loopFltrInt));

             /*--------------------------------------------------------------*
              * Has the battery started charging with a small amount of
              * current?
              *--------------------------------------------------------------*/
             if( f_instCurrent >= CHRG_MPP_MIN_CURRENT) /*Yes*/
             {
                /*-----------------------------------------------------------*
                 * Begin maximum power point tracking...
                 *-----------------------------------------------------------*/
                gt_chrgObjct.t_charge.t_state = CHARGING_TRACK;
                gt_chrgObjct.t_charge.f_prevCurrent = f_instCurrent;
             }/*End if( f_instCurrent >= CHRG_MPP_MIN_CURRENT)*/

          }/*End if( gt_chrgObjct.t_charge.f_mppUpdateTimer < 0.0f)*/

      break;

      case CHARGING_TRACK:
      {
          float f_deltaCurrent;

          gt_chrgObjct.t_charge.f_mppUpdateTimer -= gt_chrgObjct.f_dt;

          /*-----------------------------------------------------------------*
           * Is it time to run a new iteration of tracking?
           *-----------------------------------------------------------------*/
          if( gt_chrgObjct.t_charge.f_mppUpdateTimer < 0.0f)
          {

             /*--------------------------------------------------------------*
              * Find out how much the current has changed since the last
              * update.
              *--------------------------------------------------------------*/
             f_deltaCurrent = f_instCurrent - gt_chrgObjct.t_charge.f_prevCurrent;
             if( f_deltaCurrent != 0.0f)
             {
                /*-----------------------------------------------------------*
                 * If the current has decreased, change direction of PWM
                 * integration, otherwise keep integrating in the same
                 * direction.
                 *-----------------------------------------------------------*/
                if( f_deltaCurrent < 0.0f)
                {
                   gt_chrgObjct.t_charge.f_mppPwmDir =
                   -gt_chrgObjct.t_charge.f_mppPwmDir;
                }

                gt_chrgObjct.t_charge.f_loopFltrInt +=
                (gt_chrgObjct.t_charge.f_mppPwmDir*CHRG_MPP_DELTA_PWM);

             }/*End if( f_deltaCurrent != 0.0f)*/

             /*--------------------------------------------------------------*
              * Hard limit the range of PWM integration...
              *--------------------------------------------------------------*/
             if( gt_chrgObjct.t_charge.f_loopFltrInt > gt_chrgObjct.t_charge.f_maxDuty)
                gt_chrgObjct.t_charge.f_loopFltrInt = gt_chrgObjct.t_charge.f_maxDuty;
             else if( gt_chrgObjct.t_charge.f_loopFltrInt < 0)
                gt_chrgObjct.t_charge.f_loopFltrInt = 0.0f;

             /*--------------------------------------------------------------*
              * Remember the previous current...
              *--------------------------------------------------------------*/
             gt_chrgObjct.t_charge.f_prevCurrent = f_instCurrent;

             /*--------------------------------------------------------------*
              * Reset the timer...
              *--------------------------------------------------------------*/
             gt_chrgObjct.t_charge.f_mppUpdateTimer = CHRG_MPP_UPDATE_RATE;

             /*--------------------------------------------------------------*
              * Change the current going into the SEPIC converter.
              *--------------------------------------------------------------*/
             arb_ioctl( gt_chrgObjct.t_chrgHndl,
                        CHRG_CHARGER_SET_DUTY_CYCLE,
                        (uint32_t)((uint16_t)&gt_chrgObjct.t_charge.f_loopFltrInt));

          }/*End if( gt_chrgObjct.t_charge.f_mppUpdateTimer < 0.0f)*/
      }
      break;/*End case CHRG_TRACK:*/

      case CHARGING_RAMP_DOWN:
      {

         if( usr_chrgChrgLoopFilterRampDown() == true)
         {
            gt_chrgObjct.t_charge.t_state = CHARGING_IDLE;
         }

      }
      break;

      case CHARGING_MAN:
      case CHARGING_IDLE:

      break;/*End case CHARGING_IDLE:*/

   }/*End switch( gt_chrgObjct.t_charge.t_state)*/

   /*------------------------------------------------------------------------*
    * Keep track of how long we have been in the charge state.
    *------------------------------------------------------------------------*/
   gt_chrgObjct.t_charge.f_chargeTime += gt_chrgObjct.f_dt;

}/*End usr_chrgMppCharge*/

static void usr_chrgCcCharge( int32_t i_stateData,
                             uint8_t c_prevState)
{
   t_stmnError t_stErr;
   float f_instCurrent;
   bool b_chrgDet;

   if( c_prevState != CHRG_CC_CHARGE)
   {
      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 ALL_LEDS_OFF,
                 0);

      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 YELLOW_LED_ON,
                 0);

      if( i_stateData != CHRG_RETAIN_CHRG_STATE)
      {
         float f_initDutyCycle = 0.0f;

         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_CHARGER_CNTRL,
                    CHRG_DEVICE_ON);

         if( gt_chrgObjct.t_bMeas.b_dcChargeDet == true)
         {

            arb_ioctl( gt_chrgObjct.t_chrgHndl,
                        CHRG_DC_CHRG_CNTRL,
                        CHRG_DEVICE_ON);
         }
         else if( gt_chrgObjct.t_bMeas.b_acChargeDet == true)
         {
            arb_ioctl( gt_chrgObjct.t_chrgHndl,
                        CHRG_AC_CHRG_CNTRL,
                        CHRG_DEVICE_ON);
         }

         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_FAN_CNTRL,
                    CHRG_DEVICE_ON);

         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_CHARGER_SET_DUTY_CYCLE,
                    (uint32_t)((uint16_t)&f_initDutyCycle));

         /*------------------------------------------------------------------*
          * Average 50 consecutive CHRG frames.
          *------------------------------------------------------------------*/
         gt_chrgObjct.t_charge.f_alpha            = .02f;
         gt_chrgObjct.t_charge.f_loopFltrInt      = 0.0f;
         gt_chrgObjct.t_charge.f_maxDuty          = .5f;
         gt_chrgObjct.t_charge.t_state            = CHARGING_TRACK;
         gt_chrgObjct.t_charge.f_chargeTime       = 0.0f;
         gt_chrgObjct.t_charge.f_noSunWaitTimer   = 0.0f;
         gt_chrgObjct.t_charge.f_maxChargeCurrent =
         gt_chrgObjct.t_eeprom.t_limits.f_maxChargeCurrent;

      }/*End if( i_stateData != CHRG_RETAIN_CHRG_STATE)*/

      gt_chrgObjct.t_currState = CHRG_CC_CHARGE;

      /*---------------------------------------------------------------------*
       * Keep track of where we came from...
       *---------------------------------------------------------------------*/
      gt_chrgObjct.t_prevState = (t_chrgState)c_prevState;

   }/*End if( c_prevState != CHRG_CC_CHARGE)*/

   b_chrgDet = gt_chrgObjct.t_bMeas.b_acChargeDet |
   gt_chrgObjct.t_bMeas.b_dcChargeDet;

   /*------------------------------------------------------------------------*
    * Set the charge current low when the voltage is below the low threshold
    *------------------------------------------------------------------------*/
   usr_chrgCheckForTrickleCharge();

   /*------------------------------------------------------------------------*
    * Get the instantaneous current and keep track of the total number of
    * Coulombs the battery has been charged with over a lifetime of usage.
    *------------------------------------------------------------------------*/
   f_instCurrent = usr_getInstCurrentAndCountCoulombs();

   /*########################################################################*
    * Determine next state transition.
    *########################################################################*/
   if( gt_chrgObjct.c_fault)
   {
      /*---------------------------------------------------------------------*
       * Ramp down charger before changing states...
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.t_state == CHARGING_IDLE) /*Safe to change states?*/
      {

         t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                        (uint8_t)CHRG_FAULT,
                                        0);
      }
      else
         gt_chrgObjct.t_charge.t_state = CHARGING_RAMP_DOWN;
   }
   else if( gt_chrgObjct.t_bMeas.f_volts >= (gt_chrgObjct.t_eeprom.t_limits.
            f_highVoltageThr))
   {
      t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                     (uint8_t)CHRG_CV_CHARGE,
                                     (int32_t)CHRG_RETAIN_CHRG_STATE);
   }
   else if( b_chrgDet == false)
   {
      /*---------------------------------------------------------------------*
       * Ramp down charger before changing states...
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.t_state == CHARGING_IDLE) /*Safe to change states?*/
      {
         t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                        (uint8_t)CHRG_IDLE,
                                        0);
      }
      else
         gt_chrgObjct.t_charge.t_state = CHARGING_RAMP_DOWN;
   }
   else if( f_instCurrent <= (.75f*gt_chrgObjct.t_charge.f_maxChargeCurrent))
   {
      /*---------------------------------------------------------------------*
       * The panel can no longer deliver the required current do to shading,
       * find the new max...
       *---------------------------------------------------------------------*/

      /*---------------------------------------------------------------------*
       * Ramp down charger before changing states...
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.t_state == CHARGING_IDLE) /*Safe to change states?*/
      {

         t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                        (uint8_t)CHRG_MPP_CHARGE,
                                        0);
      }
      else
         gt_chrgObjct.t_charge.t_state = CHARGING_RAMP_DOWN;
   }

   switch( gt_chrgObjct.t_charge.t_state)
   {
      case CHARGING_TRACK:

         usr_chrgChrgFirstOrderLoopFilter( gt_chrgObjct.t_charge.f_maxChargeCurrent,
                                          f_instCurrent);

      break;/*End case CHRG_TRACK:*/

      case CHARGING_RAMP_DOWN:
      {

         if( usr_chrgChrgLoopFilterRampDown() == true)
         {
            gt_chrgObjct.t_charge.t_state = CHARGING_IDLE;
         }

      }
      break;

      case CHARGING_MPP_RAMP_UP:
      case CHARGING_MAN:
      case CHARGING_IDLE:

      break;/*End case CHARGING_IDLE:*/

   }/*End switch( gt_chrgObjct.t_charge.t_state)*/

   /*------------------------------------------------------------------------*
    * Keep track of how long we have been in the charge state.
    *------------------------------------------------------------------------*/
   gt_chrgObjct.t_charge.f_chargeTime += gt_chrgObjct.f_dt;

}/*End usr_chrgCcCharge*/

static void usr_chrgCvCharge( int32_t i_stateData,
                             uint8_t c_prevState)
{
   t_stmnError t_stErr;
   bool b_chrgDet;
   float f_instCurrent;

   if( c_prevState != CHRG_CV_CHARGE)
   {
      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 ALL_LEDS_OFF,
                 0);

      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 GREEN_LED_ON,
                 0);

      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 YELLOW_LED_ON,
                 0);

      if( i_stateData != CHRG_RETAIN_CHRG_STATE)
      {
         float f_initDutyCycle = 0.0f;

         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_FAN_CNTRL,
                    CHRG_DEVICE_ON);

         if( gt_chrgObjct.t_bMeas.b_dcChargeDet == true)
         {

            arb_ioctl( gt_chrgObjct.t_chrgHndl,
                        CHRG_DC_CHRG_CNTRL,
                        CHRG_DEVICE_ON);
         }
         else if( gt_chrgObjct.t_bMeas.b_acChargeDet == true)
         {
            arb_ioctl( gt_chrgObjct.t_chrgHndl,
                        CHRG_AC_CHRG_CNTRL,
                        CHRG_DEVICE_ON);
         }

         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_CHARGER_CNTRL,
                    CHRG_DEVICE_ON);

         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_CHARGER_SET_DUTY_CYCLE,
                    (uint32_t)((uint16_t)&f_initDutyCycle));

         /*------------------------------------------------------------------*
          * Average 50 consecutive CHRG frames.
          *------------------------------------------------------------------*/
         gt_chrgObjct.t_charge.f_alpha            = .02f;
         gt_chrgObjct.t_charge.f_loopFltrInt      = 0.0f;
         gt_chrgObjct.t_charge.f_maxDuty          = .5f;
         gt_chrgObjct.t_charge.t_state            = CHARGING_TRACK;
         gt_chrgObjct.t_charge.f_chargeTime       = 0.0f;
         gt_chrgObjct.t_charge.f_noSunWaitTimer   = 0.0f;
         gt_chrgObjct.t_charge.f_maxChargeCurrent =
         gt_chrgObjct.t_eeprom.t_limits.f_maxChargeCurrent;

      }/*End if( i_stateData != CHRG_RETAIN_CHRG_STATE)*/

      gt_chrgObjct.t_currState = CHRG_CV_CHARGE;

      /*---------------------------------------------------------------------*
       * Keep track of where we came from...
       *---------------------------------------------------------------------*/
      gt_chrgObjct.t_prevState = (t_chrgState)c_prevState;

   }/*End if( c_prevState != CHRG_CV_CHARGE)*/

   b_chrgDet = gt_chrgObjct.t_bMeas.b_acChargeDet |
   gt_chrgObjct.t_bMeas.b_dcChargeDet;

   /*------------------------------------------------------------------------*
    * Get the instantaneous current and keep track of the total number of
    * Coulombs the battery has been charged with over a lifetime of usage.
    *------------------------------------------------------------------------*/
   f_instCurrent = usr_getInstCurrentAndCountCoulombs();

   /*########################################################################*
    * Determine next state transition.
    *########################################################################*/
   if( gt_chrgObjct.c_fault)
   {
      /*---------------------------------------------------------------------*
       * Ramp down charger before changing states...
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.t_state == CHARGING_IDLE) /*Safe to change states?*/
      {
         t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                        (uint8_t)CHRG_FAULT,
                                        0);
      }
      else
         gt_chrgObjct.t_charge.t_state = CHARGING_RAMP_DOWN;
   }
   else if( b_chrgDet == false)
   {
      /*---------------------------------------------------------------------*
       * Ramp down charger before changing states...
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.t_state == CHARGING_IDLE) /*Safe to change states?*/
      {
         t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                        (uint8_t)CHRG_IDLE,
                                        0);
      }
      else
         gt_chrgObjct.t_charge.t_state = CHARGING_RAMP_DOWN;
   }
   else if( (f_instCurrent <= CHRG_CV_EXIT_CURRENT) &&
            (gt_chrgObjct.t_bMeas.f_volts >= (gt_chrgObjct.t_eeprom.t_limits.
             f_highVoltageThr - gt_chrgObjct.t_eeprom.t_limits.f_voltHyst)))
   {
      /*---------------------------------------------------------------------*
       * Charge finished...
       *---------------------------------------------------------------------*/

      /*---------------------------------------------------------------------*
       * Ramp down charger before changing states...
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.t_state == CHARGING_IDLE) /*Safe to change states?*/
      {

         gt_chrgObjct.t_charge.f_chargeTime  = 0.0f;
         t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                        (uint8_t)CHRG_IDLE,
                                        0);
      }
      else
         gt_chrgObjct.t_charge.t_state = CHARGING_RAMP_DOWN;
   }
   else if( gt_chrgObjct.t_bMeas.f_volts < (gt_chrgObjct.t_eeprom.t_limits.
            f_highVoltageThr - gt_chrgObjct.t_eeprom.t_limits.f_voltHyst))
   {
      /*---------------------------------------------------------------------*
       * The panel can no longer deliver the required voltage do to shading,
       * find the new max...
       *---------------------------------------------------------------------*/

      /*---------------------------------------------------------------------*
       * Ramp down charger before changing states...
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.t_state == CHARGING_IDLE) /*Safe to change states?*/
      {

         t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                        (uint8_t)CHRG_MPP_CHARGE,
                                        0);
      }
      else
         gt_chrgObjct.t_charge.t_state = CHARGING_RAMP_DOWN;
   }

   switch( gt_chrgObjct.t_charge.t_state)
   {
      case CHARGING_TRACK:

         usr_chrgChrgFirstOrderLoopFilter( gt_chrgObjct.t_eeprom.t_limits.
                                          f_highVoltageThr,
                                          gt_chrgObjct.t_bMeas.f_volts);

      break;/*End case CHRG_TRACK:*/

      case CHARGING_RAMP_DOWN:

         if( usr_chrgChrgLoopFilterRampDown() == true)
         {
            gt_chrgObjct.t_charge.t_state = CHARGING_IDLE;
         }

      break;

      case CHARGING_MPP_RAMP_UP:
      case CHARGING_MAN:
      case CHARGING_IDLE:

      break;/*End case CHARGING_IDLE:*/

   }/*End switch( gt_chrgObjct.t_charge.t_state)*/

   /*------------------------------------------------------------------------*
    * Keep track of how long we have been in the charge state.
    *------------------------------------------------------------------------*/
   gt_chrgObjct.t_charge.f_chargeTime += gt_chrgObjct.f_dt;

}/*End usr_chrgCvCharge*/

static void usr_chrgManCharge( int32_t i_stateData,
                               uint8_t c_prevState)
{
   t_stmnError t_stErr;
   bool b_chrgDet;
   float f_instCurrent;
   float f_initDutyCycle = 0.0f;

   if( c_prevState != CHRG_MAN_CHARGE)
   {
      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 ALL_LEDS_ON,
                 0);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                  CHRG_FAN_CNTRL,
                  CHRG_DEVICE_ON);

      if( gt_chrgObjct.t_bMeas.b_dcChargeDet == true)
      {

         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                     CHRG_DC_CHRG_CNTRL,
                     CHRG_DEVICE_ON);
      }
      else if( gt_chrgObjct.t_bMeas.b_acChargeDet == true)
      {
         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                     CHRG_AC_CHRG_CNTRL,
                     CHRG_DEVICE_ON);
      }

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                  CHRG_CHARGER_CNTRL,
                  CHRG_DEVICE_ON);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                  CHRG_CHARGER_SET_DUTY_CYCLE,
                  (uint32_t)((uint16_t)&f_initDutyCycle));

      /*---------------------------------------------------------------------*
       * Average 50 consecutive CHRG frames.
       *---------------------------------------------------------------------*/
      gt_chrgObjct.t_charge.f_alpha            = .05f;
      gt_chrgObjct.t_charge.f_loopFltrInt      = 0.0f;
      gt_chrgObjct.t_charge.f_maxDuty          = .5f;
      gt_chrgObjct.t_charge.t_state            = CHARGING_MAN;
      gt_chrgObjct.t_charge.f_chargeTime       = 0.0f;
      gt_chrgObjct.t_charge.f_noSunWaitTimer   = 0.0f;
      gt_chrgObjct.t_charge.f_maxChargeCurrent =
      gt_chrgObjct.t_eeprom.t_limits.f_maxChargeCurrent;

      gt_chrgObjct.t_currState = CHRG_MAN_CHARGE;

      /*---------------------------------------------------------------------*
       * Keep track of where we came from...
       *---------------------------------------------------------------------*/
      gt_chrgObjct.t_prevState = (t_chrgState)c_prevState;

   }/*End if( c_prevState != CHRG_MAN_CHARGE)*/

   b_chrgDet = gt_chrgObjct.t_bMeas.b_acChargeDet |
   gt_chrgObjct.t_bMeas.b_dcChargeDet;

   /*------------------------------------------------------------------------*
    * Get the instantaneous current and keep track of the total number of
    * Coulombs the battery has been charged with over a lifetime of usage.
    *------------------------------------------------------------------------*/
   f_instCurrent = usr_getInstCurrentAndCountCoulombs();

   /*########################################################################*
    * Determine next state transition.
    *########################################################################*/
   if( gt_chrgObjct.c_fault & ~BMS_FAULT_NO_BATT)
   {
      /*---------------------------------------------------------------------*
       * Ramp down charger before changing states...
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.t_state == CHARGING_IDLE) /*Safe to change states?*/
      {
         t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                        (uint8_t)CHRG_FAULT,
                                        0);
      }
      else
         gt_chrgObjct.t_charge.t_state = CHARGING_RAMP_DOWN;
   }
   else if( b_chrgDet == false)
   {
      /*---------------------------------------------------------------------*
       * Ramp down charger before changing states...
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.t_state == CHARGING_IDLE) /*Safe to change states?*/
      {
         t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                        (uint8_t)CHRG_IDLE,
                                        0);
      }
      else
         gt_chrgObjct.t_charge.t_state = CHARGING_RAMP_DOWN;
   }
   else if( gt_chrgObjct.t_charge.f_manPwm == 0)
   {
      /*---------------------------------------------------------------------*
       * Charge finished...
       *---------------------------------------------------------------------*/

      /*---------------------------------------------------------------------*
       * Ramp down charger before changing states...
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.t_state == CHARGING_IDLE) /*Safe to change states?*/
      {

         gt_chrgObjct.t_charge.f_chargeTime  = 0.0f;
         t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                        (uint8_t)CHRG_IDLE,
                                        0);
      }
      else
         gt_chrgObjct.t_charge.t_state = CHARGING_RAMP_DOWN;
   }

   switch( gt_chrgObjct.t_charge.t_state)
   {
      case CHARGING_MAN:

         usr_chrgChrgManLoopFilter( gt_chrgObjct.t_charge.f_manPwm);

      break;/*End case CHARGING_MAN:*/

      case CHARGING_RAMP_DOWN:

         if( usr_chrgChrgLoopFilterRampDown() == true)
         {
            gt_chrgObjct.t_charge.t_state = CHARGING_IDLE;
         }

      break;

      case CHARGING_TRACK:
      case CHARGING_MPP_RAMP_UP:
      case CHARGING_IDLE:

      break;/*End case CHARGING_IDLE:*/

   }/*End switch( gt_chrgObjct.t_charge.t_state)*/

   /*------------------------------------------------------------------------*
    * Keep track of how long we have been in the charge state.
    *------------------------------------------------------------------------*/
   gt_chrgObjct.t_charge.f_chargeTime += gt_chrgObjct.f_dt;

}/*End usr_chrgManCharge*/

static void usr_chrgFault( int32_t i_stateData,
                           uint8_t c_prevState)
{
   static bool b_fanEn = false;

   if( c_prevState != CHRG_FAULT)
   {

      if( gt_chrgObjct.c_fault & BMS_FAULT_TEMP)
      {
         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_FAN_CNTRL,
                    CHRG_DEVICE_ON);
      }

      /*---------------------------------------------------------------------*
       * Make sure the charge and discharge FETs are turned off...
       *---------------------------------------------------------------------*/
      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                 CHRG_CHARGER_CNTRL,
                 CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                  CHRG_DC_CHRG_CNTRL,
                  CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                  CHRG_AC_CHRG_CNTRL,
                  CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 ALL_LEDS_OFF,
                 0);

      gt_chrgObjct.t_currState = CHRG_FAULT;

      /*---------------------------------------------------------------------*
       * Keep track of where we came from...
       *---------------------------------------------------------------------*/
      gt_chrgObjct.t_prevState = (t_chrgState)c_prevState;
      b_fanEn = false;

   }/*End if( c_prevState != CHRG_FAULT)*/


   if( gt_chrgObjct.t_bMeas.f_temp >= 45.0f)
   {
      if( b_fanEn == false)
      {
         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_FAN_CNTRL,
                    CHRG_DEVICE_ON);
         b_fanEn = true;
      }
   }
   else
   {
      if( b_fanEn == true)
      {
         arb_ioctl( gt_chrgObjct.t_chrgHndl,
                    CHRG_FAN_CNTRL,
                    CHRG_DEVICE_OFF);
         b_fanEn = false;
      }
   }

   /*------------------------------------------------------------------------*
    * This state will not exit until a CHRG_CMD_OVER_CURR_ACK message has
    * been received from the HMI object.
    *------------------------------------------------------------------------*/

   /*------------------------------------------------------------------------*
    * Have all the faults been cleared?
    *------------------------------------------------------------------------*/
   if( gt_chrgObjct.c_fault == 0)
   {

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                 CHRG_FAN_CNTRL,
                 CHRG_DEVICE_OFF);

      /*---------------------------------------------------------------------*
       * Return to the previous state from which we came...
       *---------------------------------------------------------------------*/
      utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                           (uint8_t)gt_chrgObjct.t_prevState,
                           0);
   }

}/*End usr_chrgFault*/

static void usr_chrgPowerDown( int32_t i_stateData,
                              uint8_t c_prevState)
{
   t_eepromError t_err;

   if( c_prevState != CHRG_POWER_DOWN)
   {
      /*---------------------------------------------------------------------*
       * Make sure the charge and discharge FETs are turned off...
       *---------------------------------------------------------------------*/
      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                 CHRG_CHARGER_CNTRL,
                 CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                  CHRG_DC_CHRG_CNTRL,
                  CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                  CHRG_AC_CHRG_CNTRL,
                  CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                 CHRG_FAN_CNTRL,
                 CHRG_DEVICE_OFF);

      arb_ioctl( gt_chrgObjct.t_signalHndl,
                 ALL_LEDS_OFF,
                 0);

      gt_chrgObjct.t_currState = CHRG_POWER_DOWN;

      /*---------------------------------------------------------------------*
       * Keep track of where we came from...
       *---------------------------------------------------------------------*/
      gt_chrgObjct.t_prevState = (t_chrgState)c_prevState;

      /*---------------------------------------------------------------------*
       * Store the current settings into EEPROM.
       *---------------------------------------------------------------------*/
      t_err = utl_writeEeprom( gt_chrgObjct.t_eepromHndl,
                               (uint8_t *)&gt_chrgObjct.t_eeprom,
                               (uint16_t)sizeof( gt_chrgObjct.t_eeprom));

      if( t_err < 0)
      {
         arb_printf( PRINTF_DBG_HIGH,
                     "Failed to write EEPROM\n\r");
         exit(0);
      }

      /*---------------------------------------------------------------------*
       * Power off the motherboard...
       *---------------------------------------------------------------------*/
      arb_ioctl( gt_chrgObjct.t_chrgHndl,
                 CHRG_PWR_CNTRL,
                 CHRG_DEVICE_OFF);

      /*---------------------------------------------------------------------*
       * Wait for power down....
       *---------------------------------------------------------------------*/
      arb_sleep( ARB_TICKS_PER_SECOND*10);

   }

}/*End usr_chrgPowerDown*/

static void usr_chrgCheckForMessage( void)
{
   int8_t ac_message[CHRG_MAILBOX_SIZE];
   int16_t s_numBytes;
   t_stmnError t_stErr;

   s_numBytes = arb_mailboxRead( gt_chrgObjct.t_chrgInMbxHndl,
                                 ac_message,
                                 (uint16_t)sizeof( ac_message));

   if( s_numBytes > 0)
   {
      switch( (t_chrgMsgId)ac_message[0])
      {
         case CHRG_CMD_PWR_DOWN:

            /*---------------------------------------------------------------*
             * Transition to the power down state.
             *---------------------------------------------------------------*/
            t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                           (uint8_t)CHRG_POWER_DOWN,
                                           0);
         break;

         case CHRG_CMD_SET_LIMITS:
         {
            volatile t_chrgSetLimitsMsg *pt_chrgLimits =
            (t_chrgSetLimitsMsg *)&ac_message[0];

            if( usr_setBmsLimits( (t_chrgLimits *)&pt_chrgLimits->t_limits) ==
                false)
            {

            }
            else
            {

            }
         }
         break;

         case CHRG_CMD_SET_BOARD_SERIAL_NUM:

         break;

         case CHRG_CMD_GET_STATE:
            //Return the current state of the CHRG...CHRG_CMD_CURR_STATE
         break;

         case CHRG_CMD_FAN_CNTRL:
         {
            volatile t_chrgFanCntrlMsg *pt_fanCntrl =
            (t_chrgFanCntrlMsg *)&ac_message[0];

            /*---------------------------------------------------------------*
             * Turns on/off the fan.
             *---------------------------------------------------------------*/
            if( pt_fanCntrl->b_enabled == true)
            {
               arb_ioctl( gt_chrgObjct.t_chrgHndl,
                          CHRG_FAN_CNTRL,
                          CHRG_DEVICE_ON);
            }/*End if( pt_fanCntrl->b_enabled == true)*/
            else
            {
               arb_ioctl( gt_chrgObjct.t_chrgHndl,
                          CHRG_FAN_CNTRL,
                          CHRG_DEVICE_OFF);
            }
         }
         break;

         case CHRG_CMD_MAN_CHRG_CNTRL:
         {
            volatile t_chrgManChrgMsg *pt_chrgMan =
            (t_chrgManChrgMsg *)&ac_message[0];

            gt_chrgObjct.t_charge.f_manPwm = pt_chrgMan->f_pwm;

            /*---------------------------------------------------------------*
             * Transition to the manual charge state.
             *---------------------------------------------------------------*/
            t_stErr = utl_stMnChangeState( gt_chrgObjct.t_stateMn,
                                           (uint8_t)CHRG_MAN_CHARGE,
                                           0);
         }
         break;

         default:
            /*---------------------------------------------------------------*
             * Unknown command.
             *---------------------------------------------------------------*/
         break;

      }/*End switch( (t_chrgMsgId)ac_message[0])*/

   }/*End if( s_numBytes > 0)*/

}/*End usr_chrgCheckForMessage*/

static void usr_chrg( t_parameters t_param,
                     t_arguments  t_args)
{
   t_stmnError t_stErr;

   while( RUN_FOREVER)
   {

      /*---------------------------------------------------------------------*
       * Global data is being accessed, apply lock...
       *---------------------------------------------------------------------*/
      arb_wait( gt_chrgObjct.t_mutex, 0);

      /*---------------------------------------------------------------------*
       * Check for a new message being sent to the CHRG.
       *---------------------------------------------------------------------*/
      usr_chrgCheckForMessage();

      /*---------------------------------------------------------------------*
       * Collect new, voltage, current, and temperature measurements as well
       * as update the CHRG time step and Coulomb count.
       *---------------------------------------------------------------------*/
      usr_chrgCollectMeas();

      /*---------------------------------------------------------------------*
       * Check for any issues with the system.
       *---------------------------------------------------------------------*/
      usr_chrgFaultMonitor();

      /*---------------------------------------------------------------------*
       * Update any counters....
       *---------------------------------------------------------------------*/
      if( gt_chrgObjct.t_charge.f_noSunWaitTimer > 0.0f)
      {
         gt_chrgObjct.t_charge.f_noSunWaitTimer -= gt_chrgObjct.f_dt;
         if( gt_chrgObjct.t_charge.f_noSunWaitTimer < 0)
            gt_chrgObjct.t_charge.f_noSunWaitTimer = 0.0f;
      }/*End if( gt_chrgObjct.t_charge.f_noSunWaitTimer > 0.0f)*/

      /*---------------------------------------------------------------------*
       * Transition to the next state...
       *---------------------------------------------------------------------*/
      t_stErr = utl_stMnEngine( gt_chrgObjct.t_stateMn);

      /*---------------------------------------------------------------------*
       * unlock...
       *---------------------------------------------------------------------*/
      arb_signal( gt_chrgObjct.t_mutex);

      /*---------------------------------------------------------------------*
       * Allow other threads to run...
       *---------------------------------------------------------------------*/
      arb_sleep( CHRG_SLEEP_TICKS);

   }/*End while( RUN_FOREVER)*/

}/*End usr_chrg*/

static void usr_chrgStateMachineInit( void)
{
   uint8_t ac_transMap[CHRG_NUM_STATES][CHRG_NUM_STATES];

   /*------------------------------------------------------------------------*
    * Request access to a new state-machine object.
    *------------------------------------------------------------------------*/
   gt_chrgObjct.t_stateMn = utl_requestStMnObject( CHRG_NUM_STATES,
                                                  CHRG_BOOT);
   if( gt_chrgObjct.t_stateMn < 0)
      exit(0);

   /*------------------------------------------------------------------------*
    * Store the call-back function for each possible state transition.
    *------------------------------------------------------------------------*/
   if( utl_stMnPopFunMap( gt_chrgObjct.t_stateMn,
                          &usr_chrgBoot,
                          (uint8_t)CHRG_BOOT) < 0)
   {
      exit(0);
   }

   if( utl_stMnPopFunMap( gt_chrgObjct.t_stateMn,
                          &usr_chrgCal,
                          (uint8_t)CHRG_CAL) < 0)
   {
      exit(0);
   }

   if( utl_stMnPopFunMap( gt_chrgObjct.t_stateMn,
                          &usr_chrgCalFinished,
                          (uint8_t)CHRG_CAL_FINISHED) < 0)
   {
      exit(0);
   }

   if( utl_stMnPopFunMap( gt_chrgObjct.t_stateMn,
                          &usr_chrgIdle,
                          (uint8_t)CHRG_IDLE) < 0)
   {
      exit(0);
   }

   if( utl_stMnPopFunMap( gt_chrgObjct.t_stateMn,
                          &usr_chrgMppCharge,
                          (uint8_t)CHRG_MPP_CHARGE) < 0)
   {
      exit(0);
   }

   if( utl_stMnPopFunMap( gt_chrgObjct.t_stateMn,
                          &usr_chrgCcCharge,
                          (uint8_t)CHRG_CC_CHARGE) < 0)
   {
      exit(0);
   }

   if( utl_stMnPopFunMap( gt_chrgObjct.t_stateMn,
                          &usr_chrgCvCharge,
                          (uint8_t)CHRG_CV_CHARGE) < 0)
   {
      exit(0);
   }

   if( utl_stMnPopFunMap( gt_chrgObjct.t_stateMn,
                          &usr_chrgPowerDown,
                          (uint8_t)CHRG_POWER_DOWN) < 0)
   {
      exit(0);
   }

   if( utl_stMnPopFunMap( gt_chrgObjct.t_stateMn,
                          &usr_chrgFault,
                          (uint8_t)CHRG_FAULT) < 0)
   {
      exit(0);
   }

   if( utl_stMnPopFunMap( gt_chrgObjct.t_stateMn,
                          &usr_chrgManCharge,
                          (uint8_t)CHRG_MAN_CHARGE) < 0)
   {
      exit(0);
   }

   memset( (void *)ac_transMap, 0, sizeof( ac_transMap));

   /*------------------------------------------------------------------------*
    * Define all possible state transitions...
    *------------------------------------------------------------------------*/
   ac_transMap[CHRG_BOOT][CHRG_BOOT]  = 1;
   ac_transMap[CHRG_BOOT][CHRG_IDLE]  = 1;
   ac_transMap[CHRG_BOOT][CHRG_FAULT] = 1;

   ac_transMap[CHRG_CAL][CHRG_CAL]          = 1;
   ac_transMap[CHRG_CAL][CHRG_CAL_FINISHED] = 1;

   ac_transMap[CHRG_CAL_FINISHED][CHRG_CAL_FINISHED] = 1;
   ac_transMap[CHRG_CAL_FINISHED][CHRG_IDLE]         = 1;

   ac_transMap[CHRG_IDLE][CHRG_IDLE]       = 1;
   ac_transMap[CHRG_IDLE][CHRG_MPP_CHARGE] = 1;
   ac_transMap[CHRG_IDLE][CHRG_POWER_DOWN] = 1;
   ac_transMap[CHRG_IDLE][CHRG_FAULT]      = 1;
   ac_transMap[CHRG_IDLE][CHRG_MAN_CHARGE]        = 1;

   ac_transMap[CHRG_MPP_CHARGE][CHRG_MPP_CHARGE] = 1;
   ac_transMap[CHRG_MPP_CHARGE][CHRG_IDLE]       = 1;
   ac_transMap[CHRG_MPP_CHARGE][CHRG_CC_CHARGE]  = 1;
   ac_transMap[CHRG_MPP_CHARGE][CHRG_CV_CHARGE]  = 1;
   ac_transMap[CHRG_MPP_CHARGE][CHRG_POWER_DOWN] = 1;
   ac_transMap[CHRG_MPP_CHARGE][CHRG_FAULT]      = 1;

   ac_transMap[CHRG_CC_CHARGE][CHRG_CC_CHARGE]  = 1;
   ac_transMap[CHRG_CC_CHARGE][CHRG_CV_CHARGE]  = 1;
   ac_transMap[CHRG_CC_CHARGE][CHRG_MPP_CHARGE] = 1;
   ac_transMap[CHRG_CC_CHARGE][CHRG_IDLE]       = 1;
   ac_transMap[CHRG_CC_CHARGE][CHRG_POWER_DOWN] = 1;
   ac_transMap[CHRG_CC_CHARGE][CHRG_FAULT]      = 1;

   ac_transMap[CHRG_CV_CHARGE][CHRG_CV_CHARGE]  = 1;
   ac_transMap[CHRG_CV_CHARGE][CHRG_MPP_CHARGE] = 1;
   ac_transMap[CHRG_CV_CHARGE][CHRG_IDLE]       = 1;
   ac_transMap[CHRG_CV_CHARGE][CHRG_POWER_DOWN] = 1;
   ac_transMap[CHRG_CV_CHARGE][CHRG_FAULT]      = 1;

   ac_transMap[CHRG_MAN_CHARGE][CHRG_MAN_CHARGE] = 1;
   ac_transMap[CHRG_MAN_CHARGE][CHRG_IDLE]       = 1;
   ac_transMap[CHRG_MAN_CHARGE][CHRG_FAULT]      = 1;

   ac_transMap[CHRG_FAULT][CHRG_FAULT]      = 1;
   ac_transMap[CHRG_FAULT][CHRG_BOOT]       = 1;
   ac_transMap[CHRG_FAULT][CHRG_MPP_CHARGE] = 1;
   ac_transMap[CHRG_FAULT][CHRG_CC_CHARGE]  = 1;
   ac_transMap[CHRG_FAULT][CHRG_CV_CHARGE]  = 1;
   ac_transMap[CHRG_FAULT][CHRG_IDLE]       = 1;
   ac_transMap[CHRG_FAULT][CHRG_POWER_DOWN] = 1;
   ac_transMap[CHRG_FAULT][CHRG_MAN_CHARGE] = 1;

   /*------------------------------------------------------------------------*
    * Store the state transition map.
    *------------------------------------------------------------------------*/
   utl_stMnPopTransMap( gt_chrgObjct.t_stateMn,
                        &ac_transMap[0][0]);

}/*End usr_chrgStateMachineInit*/

/*---------------------------------------------------------------------------*
 * Public Function Definitions
 *---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*
 * Returns the time remaining in the deep discharge state until the
 * motherboard is powered down.
 *---------------------------------------------------------------------------*/
int8_t usr_getDeepDisTimeRem( void)
{
   return gt_chrgObjct.c_deepDisTimeRem;
}/*End usr_getDeepDisTimeRem*/

/*---------------------------------------------------------------------------*
 * Returns the last known battery voltage, current, temperature, fuel gage
 * and charging status.
 *---------------------------------------------------------------------------*/
t_chrgMeasStatus usr_getBmsMeas( void)
{
   t_chrgMeasStatus t_status;

   t_status.t_meas  = gt_chrgObjct.t_bMeas;
   t_status.t_state = gt_chrgObjct.t_currState;
   t_status.c_fault = gt_chrgObjct.c_fault;

   return t_status;

}/*End usr_getBmsMeas*/

/*---------------------------------------------------------------------------*
 * This function returns the mailbox handle for sending messages to the CHRG.
 *---------------------------------------------------------------------------*/
t_MAILBOXHNDL usr_getBmsInMlbxHndle( void)
{
   return gt_chrgObjct.t_chrgInMbxHndl;
}/*End usr_getBmsInMlbxHndle*/

/*---------------------------------------------------------------------------*
 * This function returns the mailbox handle for receiving messages from the
 * CHRG.
 *---------------------------------------------------------------------------*/
t_MAILBOXHNDL usr_getBmsOutMlbxHndle( void)
{
   return gt_chrgObjct.t_chrgOutMbxHndl;
}/*End usr_getBmsOutMlbxHndle*/

t_error usr_chrgInit( void)
{
   t_mailboxConfig t_mConfig;

   /*------------------------------------------------------------------------*
    * Initialize the CHRG object...
    *------------------------------------------------------------------------*/
   memset( (void *)&gt_chrgObjct, 0, sizeof( t_chrgObjct));

   /*------------------------------------------------------------------------*
    * Create a new thread.
    *------------------------------------------------------------------------*/
   gt_chrgObjct.t_chrgThread = arb_threadCreate( usr_chrg,
                                               1,
                                               0,
                                               ARB_STACK_768B,
                                               0);

   if( gt_chrgObjct.t_chrgThread < 0)
   {
      return gt_chrgObjct.t_chrgThread;

   }/*End if( gt_chrgObjct.t_chrgThread < 0)*/

   /*------------------------------------------------------------------------*
    * Open a handle to the console driver.
    *------------------------------------------------------------------------*/
   gt_chrgObjct.t_signalHndl = arb_open( "signalDevice0",
                                        ARB_O_READ |
                                        ARB_O_WRITE);

   if( gt_chrgObjct.t_signalHndl < 0)
   {
      return gt_chrgObjct.t_signalHndl;
   }

   /*------------------------------------------------------------------------*
    * Request access to a semaphore which will be used for locking access
    * to the CHRG object.
    *------------------------------------------------------------------------*/
   gt_chrgObjct.t_mutex = arb_semaphoreCreate( MUTEX);

   if( gt_chrgObjct.t_mutex < 0)
   {
      return gt_chrgObjct.t_mutex;
   }

   /*------------------------------------------------------------------------*
    * Request access to a semaphore which will be used for letting the CHRG
    * know that the user has properly acknowledged an error condition.
    *------------------------------------------------------------------------*/
   gt_chrgObjct.t_chrgLimitAck = arb_semaphoreCreate( SIGNAL);

   if( gt_chrgObjct.t_chrgLimitAck < 0)
   {
      return gt_chrgObjct.t_chrgLimitAck;
   }

   /*------------------------------------------------------------------------*
    * Open a handle to the CHRG driver.
    *------------------------------------------------------------------------*/
   gt_chrgObjct.t_chrgHndl = arb_open( "chrgDevice0",
                                     ARB_O_READ |
                                     ARB_O_WRITE);

   if( gt_chrgObjct.t_chrgHndl < 0)
   {
      return gt_chrgObjct.t_chrgHndl;
   }

   /*------------------------------------------------------------------------*
    * Create a mailbox for incoming messages to the CHRG.
    *------------------------------------------------------------------------*/
   t_mConfig.s_queueSize  = CHRG_MAILBOX_SIZE;
   t_mConfig.s_queueDepth = 1;
   t_mConfig.t_writeMode  = BLOCKING;    /*Threads writing to CHRG block*/
   t_mConfig.t_readMode   = NONBLOCKING; /*CHRG reading does not block*/
   t_mConfig.b_wrtFromInt = false;
   t_mConfig.b_multRdWr   = true;

   gt_chrgObjct.t_chrgInMbxHndl = arb_mailboxCreate( t_mConfig);

   if( gt_chrgObjct.t_chrgInMbxHndl < 0)
   {
      return gt_chrgObjct.t_chrgInMbxHndl;
   }

   /*------------------------------------------------------------------------*
    * Create a mailbox for outgoing messages from the CHRG.
    *------------------------------------------------------------------------*/
   t_mConfig.s_queueSize  = CHRG_MAILBOX_SIZE;
   t_mConfig.s_queueDepth = 1;
   t_mConfig.t_writeMode  = NONBLOCKING; /*CHRG writing does not block*/
   t_mConfig.t_readMode   = BLOCKING;    /*Threads reading from CHRG block*/
   t_mConfig.b_wrtFromInt = false;
   t_mConfig.b_multRdWr   = true;

   gt_chrgObjct.t_chrgOutMbxHndl = arb_mailboxCreate( t_mConfig);

   if( gt_chrgObjct.t_chrgOutMbxHndl < 0)
   {
      return gt_chrgObjct.t_chrgOutMbxHndl;
   }

   /*------------------------------------------------------------------------*
    * Request access to a block of EEPROM...
    *------------------------------------------------------------------------*/
   gt_chrgObjct.t_eepromHndl =
   utl_requestEepromBlock( CHRG_EEPROM_ID,
                           (int16_t)sizeof( gt_chrgObjct.t_eeprom));

   /*------------------------------------------------------------------------*
    * Has the block already been allocated?
    *------------------------------------------------------------------------*/
   if( gt_chrgObjct.t_eepromHndl == EEPROM_ALREADY_ACTIVE_USERID)
   {
      gt_chrgObjct.t_eepromHndl = utl_getEepromHandle( CHRG_EEPROM_ID);
      if( gt_chrgObjct.t_eepromHndl < 0)
      {
         return gt_chrgObjct.t_eepromHndl;
      }

   }/*End if( gt_chrgObjct.t_eepromHndl == EEPROM_ALREADY_ACTIVE_USERID)*/
   else
   {
      usr_storeEepromFactDefaults();
   }

   usr_chrgStateMachineInit();

   /*------------------------------------------------------------------------*
    * See if there are volt, current, and temp meter calibration parameters,
    * as well as battery limits stored in EEPROM. If not, load the factory
    * default parameters.
    *------------------------------------------------------------------------*/
   usr_getValuesFromEeprom();

   /*------------------------------------------------------------------------*
    * Tell the driver the current calibration settings.
    *------------------------------------------------------------------------*/
   arb_ioctl( gt_chrgObjct.t_chrgHndl,
              CHRG_STORE_CAL,
              (uint32_t)((uint16_t)&gt_chrgObjct.t_eeprom.t_cal));

   return ARB_PASSED;

}/*End usr_chrgInit*/
